/*
 * Copyright 2008 Jason Coco (CoLa Code)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <math.h>
#include <bzlib.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#import "BZ2FileHandle.h"


@implementation NSFileHandle (BZ2FileHandleOperations)

-(NSData *)readBZ2DataToEndOfFile
{
	// Get the compressed data already in this file... this data
	// turns out not to be BZ2 compressed data, we'll return this
	// object unmolested.
	NSData *originalData = [self readDataToEndOfFile];
	
	// If nothing came back, simply pass it along
	if( !originalData ) return nil;
	
	// The data that will eventually be returned by this method
	NSData *returnData;
	
	// Get the original length and assume a buffer with enough
	// capacity to do a 1:1.5 decompression ratio on the first
	// pass
	NSUInteger originalLength = [originalData length];
	NSUInteger bufferLength   = (NSUInteger)ceil(originalLength * 1.5);
	
	// Create the data buffer that we'll initially use to hold the
	// uncompressed data
	NSMutableData *newData = [[[NSMutableData alloc] initWithCapacity:bufferLength] autorelease];
	
	// Set up the decompression stream structures
	bz_stream strm;
	strm.bzalloc  = NULL;
	strm.bzfree   = NULL;
	strm.opaque   = NULL;
	strm.next_in  = (char *)[originalData bytes];
	char *buf     = calloc(bufferLength * sizeof(char), 1);
	strm.next_out = buf;
	
	// Ensure that calloc returned a valid address
	if( buf == NULL ) {
		NSLog(@"Could not allocate memory for the buffer: %s", strerror(errno));
		return nil;
	}
	
	// Initialize the decompression stream and deal with malformed data
	int bzerror = BZ2_bzDecompressInit(&strm, 0, 0);
	if( bzerror != BZ_OK ) {
		// Something went wrong with the initialization... this is either
		// a library error or we've run out of memory... either way, we
		// can't recover. Clean up and return nil for now: TODO: have this
		// raise an exception letting the application possibly deal with
		// low memory situations or alerting the administrator to a malformed
		// bz library installation...
		NSLog(@"It's just not gonna happen: %d", bzerror);
		free(buf);
		return nil;
	}
	
	// The stream is initialized and we'll attempt to decompress the data
	// and build a new NSData structure with the decompressed data
	strm.avail_in  = originalLength;
	strm.avail_out = bufferLength;
	while( (bzerror = BZ2_bzDecompress(&strm)) == BZ_OK ) {
		// There is more uncompressed data than available space
		// in our buffer
		[newData appendBytes:buf
					  length:bufferLength - strm.avail_out];
		// Reset the buffer
		memset(buf, 0, bufferLength);
		strm.avail_out = bufferLength;
		strm.next_out  = buf;
	}
	
	// Check to make sure that this returned because we reached the end of
	// our data stream... otherwise handle the error situation as best as
	// possible...
	if( bzerror == BZ_STREAM_END ) {
		[newData appendBytes:buf
					  length:bufferLength - strm.avail_out];
		returnData = [NSData dataWithData:newData];
	} else if( bzerror == BZ_DATA_ERROR_MAGIC ) {
		// This stream isn't actually a bzip2 compressed stream... return
		// the original data and clean up
		returnData = originalData;
	} else {
		// Something else happened that we can't really recover from... so
		// we'll return whatever we have, or nil if we have nothing and then
		// clean up. We'll also log this error...
		NSLog([NSString stringWithFormat:@"Error decompressing bz2 stream: %d", bzerror]);
		if( [newData length] > 0 )
			returnData = [NSData dataWithData:newData];
		else
			returnData = nil;
	}
	
	// Close up and release memory associated with the decompression routines
	BZ2_bzDecompressEnd(&strm);
	free(buf);
	
	return returnData;
}

@end